package au.edu.swin.MelbourneMap;

import java.util.*;
import java.io.*;
import java.awt.geom.*;

/**
 * Data structure to define a road network. 
 *
 * @author  Alexis Wellavize (s/n: 6033482)<br/>
 * @since   v1, created:  24/10/2012
 * @version v1, modified:  24/10/2012
 *
 */

public class RoadData
{
	// Lookup of adjacency nodes in graph
	//private HashMap<Node, Graph<Point2D> > nodeLookup =
    //    new HashMap<Node, Graph<Point2D>>();
    
    // Lookup of roads in the network (sorted)
    private TreeMap<String, ArrayList<Road>> roadLookup = 
        new TreeMap<String, ArrayList<Road>>();
        
    // Dijkstra graph of connected nodes
    private DiGraph dg = new DiGraph();
    
    // Data filename 
    private String filename;
    
    
    //TEST
    private String distance;

	// Read road data file and construct directed graph of nodes
	public void readFile() 
		throws IOException
	{
        // Line tokens in input data
        String[] tokens = null;
        // Input line data
        String line = "";
        // Name of road
        String name = "";
        // Type of road
        String type = "";
        // Start node of road segment
        Point2D p1 = null; 
        // End node of road segment
        Point2D p2 = null;
        // Number of lanes
        int lanes = 0;
        // Easting UTM coordinates of first node
        double e1 = 0.0;
        // Northing UTM coordinates of first node
        double n1 = 0.0;
        // Easting UTM coordinates of second node
        double e2 = 0.0;
        // Northing UTM coordinates of second node
        double n2 = 0.0;
        // Edge distance between nodes (in meters)
        double distance = 0.0;
        
        // Open input data file for reading
        LineNumberReader input =
            new LineNumberReader(
                new FileReader( filename ) );
        
        // Road list
        LinkedList<Road> roadList = new LinkedList<Road>();
        
        // Read first line
        line = input.readLine();
        
        // While not at end of data ...
        while( !line.equals( "finalend" ) )
        {
            // While within a region of data ...
            while( !line.equals( "end" ) )
            {
                // Split line into tokens
                tokens = line.split( "\\|{1}" );
                
                // Read road data from line in format:
                //<road name>|<e1>|<n1>|<e2>|<n2>|<type>|<lanes>
                name = tokens[0];
				e1 = Double.parseDouble( tokens[1] );
				n1 = Double.parseDouble( tokens[2] );
				e2 = Double.parseDouble( tokens[3] );
				n2 = Double.parseDouble( tokens[4] );
                type = tokens[5];
                lanes = Integer.parseInt( tokens[6] );
                
                // Calculate edge distance between nodes with Pythagoras theorem
                distance = Math.sqrt((e2 - e1)*(e2 - e1) + (n2 - n1)*(n2 - n1));
                
                // Create start and end node
                p1 = new Point2D.Double( e1, n1 );
                p2 = new Point2D.Double( e2, n2 );
                
                // Create nodes for graph
                Vertex v1 = dg.getVertex( p1 );
                if (v1 == null)
                    v1 = dg.createVertex( p1 );
                
                Vertex v2 = dg.getVertex( p2 );
                if (v2 == null)
                    v2 = dg.createVertex( p2 );
                
                // Create directed graph based on road direction
                if ( type.equals( "2" ) )             
                {
                    // p1 <-> p2 
                    dg.addEdge( v1, v2, p1.distance( p2 ) );
                    dg.addEdge( v2, v1, p2.distance( p1 ) );
                }
                else if ( type.equals( "F" ) )
                {
                    // p1 -> p2
                    dg.addEdge( v1, v2, p1.distance( p2 ));
                }
                else if ( type.equals( "B" ) )
                {
                    // p1 <- p2
                    dg.addEdge( v2, v1, p2.distance( p1 ) );
                }
                
                // AW---Add road segment
                addRoadSegment( p1, p2, name, distance, type, lanes );
                
                // AW --- Not working as expected
               // roadList.add( new Road( name, p1, p2, distance, type, lanes ) );
                
                // Read next line from data file
                line = input.readLine();
            } 
            
            // --- Not working as expected
           // connectRoads( name, roadList );
            //roadList.clear();

            line = input.readLine();
        }
        
        // Close input data file
        input.close();
    }
    
    // Add a road segment to network
    private void addRoadSegment(
        Point2D p1, Point2D p2,
        String name, double distance, String type, int lanes )
    {
        // Inititialise variables
        Road road = null;
        boolean startNodeFound = false;
        boolean endNodeFound = false;
        boolean createNewRoad = false;
        double e1 = p1.getX();
        double e2 = p2.getX();
        double n1 = p1.getY();
        double n2 = p2.getY();
        ArrayList<Road> roadList = new ArrayList<Road>();
        
        // Check whether the road has already been defined
        if ( roadLookup.containsKey( name ) )
        {
            // Get list of all roads defined with same name
            roadList = roadLookup.get( name );
            
            // Check if list contains one or multiple roads with same name
            if ( roadList.size() > 1 )
            {
                // Multiple roads exist with same name - Loop through roads
                for ( int i = 0; i < roadList.size(); i++ )
                {
                    // Get the existing road from list
                    road = roadList.get(i);
                   
                    // Check if existing road contains either start or end node
                    if ( road.getNodes().contains( p1 ) )
                    {
                        startNodeFound = true;
                        break;
                    }
                    else if ( road.getNodes().contains( p2 ) )
                    {
                        endNodeFound = true;
                        break;                    
                    }
                }
               
                // Check whether to create a new road
                if ( !startNodeFound && !endNodeFound )
                    createNewRoad = true;
            }
            else
            {
                // Only one road exists with name - Get the road
                road = roadList.get(0);
                    
                // Check if existing road contains either start or end node
                if ( road.getNodes().contains( p1 ) )
                    startNodeFound = true;
                else if ( road.getNodes().contains( p2 ) )
                    endNodeFound = true;
                else
                    createNewRoad = true;
            }
        }
        else
        {
            // No road exists with name - Flag a new road creation
            createNewRoad = true;
        }
        
         // A new road needs to be created
        if ( createNewRoad )
        {
            // TODO: Determine suburb road is in ...
            
            // Create a new road with name
            road = new Road( name );
            // Add the start node to the road
            road.addFirstNode( p1 );
            // Add the end node to the road
            road.addLastNode( p2 );
            // Add the calculated distance to the road
            road.addDistance( distance );
            // Set the type of road
            road.setType( type );
            // Set the number of lanes
            road.setLanes( lanes );
            // Add new road to road list
            roadList.add( road );
            // Add roadList to road lookup (overwrites) 
            roadLookup.put( name, roadList );
        }
        else
        {
            // Add start or end node
            if ( startNodeFound )
            {
                if ( road.getFirstNode().equals( p1 ) )
                    road.addFirstNode( p2 );
                else if ( road.getLastNode().equals( p1 ) )
                    road.addLastNode( p2 );
            }
            else if ( endNodeFound )
            {
                if ( road.getFirstNode().equals( p2 ) )
                    road.addFirstNode( p1 );
                else if ( road.getLastNode().equals( p2 ) )
                    road.addLastNode( p1 );
            }        
        }

    }

    // Write road data file from defined road network
    public void writeFile()
        throws IOException
    {
    	String tempName = "";
        // Open data file for writing
        BufferedWriter output =
            new BufferedWriter( new FileWriter( filename ) );
    
        Point2D p1 = null;
        Point2D p2 = null;
        for( ArrayList<Road> roadList : roadLookup.values() )
    	{
            for( Road r : roadList )
            {
            	//if(!r.getName().equals(tempName) && !tempName.equals(""))
          		  //output.write( "end\n" );
            	
                for( int i = 0; i < r.getNodes().size() - 1; i++ )
                {
                	p1 = r.getNodes().get(i);
                    p2 = r.getNodes().get(i + 1);

                    output.write(
                        r.getName() + "|" +
                        (int)p1.getX() + "|" + 
                        (int)p1.getY() + "|" +
                        (int)p2.getX() + "|" +
                        (int)p2.getY() + "|" +
                        r.getType() + "|" +
                        (int)r.getLanes() + "\n" );
                    tempName = r.getName();
                }
                output.write( "end\n" );
                output.flush(); 
            }  
        }
        //output.write( "end\n" );

        output.write( "finalend\n" );  
        output.flush();
        output.close();
    }
    
    // Find the road nearest to a point
    public Road findNearestRoad( Point2D p1, int radius )
    {
        double minDistance = Double.POSITIVE_INFINITY;
        double distance = 0.0;
        double e1 = 0.0;
        double e2 = 0.0;
        double n1 = 0.0;
        double n2 = 0.0;
        Road nearestRoad = null;
        
        e1 = p1.getX();
        n1 = p1.getY();
        
        //Rectangle2D r2 =
        //    new Rectangle2D.Double(
        //        e1 - radius, n1 - radius, 2 * radius, 2 * radius );
        
        System.out.println( "point clicked: " + e1 + ", " + n1 );
        
        for( ArrayList<Road> roadList : roadLookup.values() )
    	{
            for( Road r : roadList )
            {
                for( Point2D p2 : r.getNodes() )
                {
                    e2 = p2.getX();
                    n2 = p2.getY();
               
                    //if ( r2.contains( p2 ) )
                    //{
                        distance = Math.sqrt((e2 - e1)*(e2 - e1) + (n2 - n1)*(n2 - n1));
                    
                        
                    
                        if ( Math.min( distance, minDistance ) < minDistance )
                        {
                        	System.out.println( "road near point: " + r.getName() + " distance: " + distance );
                            minDistance = Math.min( distance, minDistance );
                            nearestRoad = r;
                        }
                    //}
                }
            }
        }
        return nearestRoad;
    }
    
    
    // Replace nearestNode
    public void replacePoint( Point2D oldPoint, Point2D newPoint)//, int radius )
    {
        /*double minDistance = Double.POSITIVE_INFINITY;
        double distance = 0.0;
        double e1 = 0.0;
        double e2 = 0.0;
        double n1 = 0.0;
        double n2 = 0.0;
        Road nearestRoad = null;*/
        
        //e1 = p1.getX();
       // n1 = p1.getY();
        for( ArrayList<Road> roadList : roadLookup.values() )
    	{
        	
            for( Road r : roadList )
            {
            	
                for( Point2D p2 : r.getNodes() )
                {
                	if(p2.equals(oldPoint))
                		r.replaceNode(newPoint);
                }
            }
        }
        
    }
    
 //ADDED find nearst Point to disply box
    public Point2D findNearestPoint( Point2D p1, int radius )
    {
        double minDistance = Double.POSITIVE_INFINITY;
        double distance = 0.0;
        double e1 = 0.0;
        double e2 = 0.0;
        double n1 = 0.0;
        double n2 = 0.0;
        Point2D nearestPoint = null;
        
        e1 = p1.getX();
        n1 = p1.getY();
        
      //  System.out.println( "point clicked: " + e1 + ", " + n1 );
        
        for( ArrayList<Road> roadList : roadLookup.values() )
    	{
            for( Road r : roadList )
            {
                for( Point2D p2 : r.getNodes() )
                {
                    e2 = p2.getX();
                    n2 = p2.getY();
               
                   
                        distance = Math.sqrt((e2 - e1)*(e2 - e1) + (n2 - n1)*(n2 - n1));
                    
                        
                    
                        if ( Math.min( distance, minDistance ) < minDistance )
                        {
                        	//System.out.println( "road near point: " + r.getName() + " distance: " + distance );
                            minDistance = Math.min( distance, minDistance );
                            nearestPoint = p2;//nearestRoad = r;
                        }
                    //}
                }
            }
        }
        return nearestPoint;
    }
    
    // Return shortest path between two points as Path2D
    public Path2D getShortestPath(
        Point2D p1, Point2D p2, int radius, String units )
    {

        // Calculate shortest path between two points
        dg.dijkstra( p1, radius );
        Vertex w = dg.getPath( p2, radius, units );
        dg.printPath( w );
        List<Vertex> sp = dg.getShortestPath();
        
        
        // Create Path2D to be drawn
        Path2D path = new Path2D.Double();
        for ( int i = 0; i < sp.size() - 1; i++  )
        {
            Point2D pp1 = sp.get(i).getPoint2D();
            Point2D pp2 = sp.get(i + 1).getPoint2D();
            path.moveTo( pp1.getX(), pp1.getY() );
            path.lineTo( pp2.getX(), pp2.getY() );
        }
        setDistance(dg.getStr());
        return path;
    }
    
    public ArrayList<Road> getRoads()
    {
        ArrayList<Road> roads = new ArrayList<Road>();
        for( ArrayList<Road> roadList : roadLookup.values() )
            roads.addAll( roadList );
            
        return roads;
    }

    public void writeFile( String filename )
        throws IOException
    {
        this.filename = filename;
        writeFile();
    }
    
    public void setDataFileName( String filename )
        throws IOException
    {
        this.filename = filename;
    }
    
    
    // 27/10/2012 - AW --- NOT working as expected
    private void connectRoads( String name, LinkedList<Road> stack )
    {
        LinkedList<Road> addList = new LinkedList<Road>();
        LinkedList<Road> removeList = new LinkedList<Road>();
        
        ArrayList<Road> connectedRoads = new ArrayList<Road>();
        
        boolean found = false;
        boolean finished = false;
        
        int count = 0;
        int size = 0;
         
        while ( !finished )
        {
        	
            size = stack.size();
          //  System.out.println("SIZE beotre: "+ size);
            if ( size == 1 )
            {
            	//System.out.println("SIZE==1");
                break;
            }
       
            // De-duplicate data
            Road r1 = stack.remove();
            r1.setName( name );
            
            for ( Road r2 : stack )
            {
                if (
                    r1.getFirstNode().equals( r2.getFirstNode() ) &&
                    r1.getLastNode().equals( r2.getLastNode() ) )
                {
                    removeList.add( r2 );
                }
            }

            //for ( Road r : addList )
            //    stack.add( r );
                    
            for ( Road r : removeList )
                stack.remove( r );                
                
                
            stack.addLast( r1 );
            
            
            removeList.clear();
            //addList.clear();  
            
            if ( size == stack.size() )
                count++;
                
            if ( count >= size )
                break;

        }
        
        count = 0;
        size = 0;
        
        Boolean flag1 = false;
       
       size = stack.size();
       
        while(!stack.isEmpty())
        {
        	
        	Road r1 = stack.remove();
        	
        	if(connectedRoads.isEmpty())
        		connectedRoads.add(r1);
        	else
        	{
        		for(Road r2 : connectedRoads)
        		{
        			if ( r1.getFirstNode().equals( r2.getLastNode() ) )
                    {
                        // Add r2 -> r1
                        System.out.println("Add r2 -> r1");
                        r2.addPoints( r1.getNodes() );
                        break;
                    
                    }
                    else if ( r1.getLastNode().equals( r2.getFirstNode() ) )
                    {
                        // Add r1 -> r2
                    	System.out.println("Add r1 -> r2");
                        r1.addPoints( r2.getNodes() );
                    	
                        r2 = r1;
                        break;
                    }
                    else
                    {
                    	flag1 = true;
                    	
                    }
        			
        		}
        		
        		if(flag1)
        		{
        			connectedRoads.add(r1);
        			if(!addList.contains(r1))
        				addList.addLast(r1);
        			flag1 = false;
        		}
        	}
        	count++;
        	
        }
        
        if(size != connectedRoads.size())
        	connectRoads(name, new LinkedList<Road>(connectedRoads));
        
        	

        
       
        	

        
        /*
        // Link road segments
        while ( !finished )
        {
            size = stack.size();
            System.out.println("Size Before : "+size);            
            if ( size == 1 )
                break;
            
            
            Road r1 = stack.remove();
            
            for ( Road r2 : stack )
            {
            	
                found = false;
                
                if ( r1.getFirstNode().equals( r2.getLastNode() ) )
                {
                    // Add r2 -> r1
                    System.out.println("Add r2 -> r1");
                    removeList.add( r2 );
                    r2.addPoints( r1.getNodes() );
                    addList.add( r2 );
                    
                    found = true;
                    break;

                }
                else if ( r1.getLastNode().equals( r2.getFirstNode() ) )
                {
                    // Add r1 -> r2
                    
                	removeList.add( r2 );
                	
                    r1.addPoints( r2.getNodes() );
                    addList.add( r1 );
                      
                    found = true;
                    System.out.println("Add r1 -> r2");
                    break;
                }
                else
                {
                    // r1 and r2 are not connected, add back in list
                    System.out.println("r2 and r1 not connected");
                     addList.addLast(r1);
                     addList.addLast(r2);
                     found = false;
                }
            }
            System.out.println("1");
           // if (!found)
           // 	stack.addLast( r1 );

            if(removeList.size() > 0)
            {System.out.println("5");
            	for ( Road r : removeList )
            		stack.remove( r );
            	System.out.println("6");
            }
            
            
            System.out.println("2");
            if(addList.size() > 0)
            {
            	System.out.println("3");
            	for ( Road r : addList )
            		stack.add( r );
            	System.out.println("4");
            }
             

            System.out.println("7");
            removeList.clear();
            addList.clear();
            System.out.println("8");

            
            if ( size == stack.size() )
                count++;
                
            if ( count >= size || size < 1000)
                break;
             
        }
        */
        
        // Add roadList to road lookup (overwrites)
        System.out.println("9");
        roadLookup.put( name, connectedRoads);
        
    }
      
    

    // TESTING
    public String toString()
    {
        StringWriter output = new StringWriter();
        
        /*
        // DEBUG: Loop through road list (working)
        for( ArrayList<Road> roadList : roadLookup.values())
    	{
            for( Road r : roadList )
            {
                output.append(
                    "Road (" + r.getName() +
                    ", " + r.getDistance() + ")\n" );
            }
        }
        */
        
       /* 
        // DEBUG: Write file (working)
        Point2D p1 = null;
        Point2D p2 = null;
        
        for( ArrayList<Road> roadList : roadLookup.values() )
    	{
            for( Road r : roadList )
            {
                output.append("new road\n");
                
                for( int i = 0; i < r.getNodes().size() - 1; i++ )
                {
                    p1 = r.getNodes().get(i);
                    p2 = r.getNodes().get(i + 1);

                    output.append(
                        r.getName() + "|" +
                        (int)p1.getX() + "|" + 
                        (int)p1.getY() + "|" +
                        (int)p2.getX() + "|" +
                        (int)p2.getY() + "|" +
                        r.getType() + "|" +
                        (int)r.getLanes() + "\n" ); 
                }
            }    
            
            output.append( "end\n" );
        }
        output.append( "finalend\n" );
        
        */
        /*
        // DEBUG: Find nearest road (works)
        //KING ST|337206|5817500|336771|5817552|2|2
        Point2D point = new Point2D.Double( 337206.0, 5817501.0 );
        Road road = findNearestRoad( point );
        output.append(
            "Nearest road to point (" + point.getX() +
            "," + point.getY() + ") is " + road.getName() );
        */

        /*
        // DEBUG: Print road node count (works)
        for( ArrayList<Road> roadList : roadLookup.values())
    	{
            for( Road r : roadList )
            {
                output.append(
                    "Road (" + r.getName() +
                    ", " + r.getNodes().size() + ")\n" );
            }
        }
        */
        
        return output.toString();
        //return "";
    }

	public void setDistance(String distance) {
		this.distance = distance;
	}

	public String getDistance() {
		return distance;
	}
}
